﻿#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>		//	增加的 Linux open 头文件
#include <sys/socket.h>		//	增加的 Linux 网络通信 头文件
#include <arpa/inet.h>		//	增加的 Linux 网络通信 头文件
#include <netinet/in.h>		//	增加的 Linux 网络通信 头文件
#include <sys/stat.h>		//	增加的 Linux open 头文件
#include <fcntl.h>			//	增加的 Linux open 头文件

	// ①编译服务器操作:		
	// 	cd /mnt/hgfs/.../Linux_aitalk_exp1227_d2bda845/samples/asr_offline_sample/
	// 	./64bit_make.sh
		
	// ②运行程序操作:		
	// 	cd /mnt/hgfs/.../Linux_aitalk_exp1227_d2bda845/bin
	// 	./asr_offline_sample
		
int sockfd = -1;					//	增加的 Linux 网络通信 套接字文件
struct sockaddr_in client;			//	增加的 网络通信 服务器保存客户端的 网络地址,就是板子的网络地址
socklen_t addrlen = sizeof(client);	//	增加的 保存客户端的 网络地址 的长度,就是板子的网络地址长度

//	29 53 64~66 100 163 178 186 193 196 230 269~282 有所增加修改
#include "../../include/qisr.h"
#include "../../include/msp_cmn.h"
#include "../../include/msp_errors.h"

#define SAMPLE_RATE_16K     (16000)
#define SAMPLE_RATE_8K      (8000)
#define MAX_GRAMMARID_LEN   (32)
#define MAX_PARAMS_LEN      (1024)

const char * ASR_RES_PATH        = "fo|res/asr/common.jet"; //离线语法识别资源路径
const char * GRM_BUILD_PATH      = "res/asr/GrmBuilld"; //构建离线语法识别网络生成数据保存路径
const char * GRM_FILE            = "yysb.bnf"; //构建离线识别语法网络所用的语法文件	需要修改!!!!
const char * LEX_NAME            = "contact"; //更新离线识别语法的contact槽（语法文件为此示例中使用的call.bnf）

typedef struct _UserData {
	int     build_fini; //标识语法构建是否完成
	int     update_fini; //标识更新词典是否完成
	int     errcode; //记录语法构建或更新词典回调错误码
	char    grammar_id[MAX_GRAMMARID_LEN]; //保存语法构建返回的语法ID
}UserData;


const char *get_audio_file(void); //选择进行离线语法识别的语音文件
int build_grammar(UserData *udata); //构建离线识别语法网络
int update_lexicon(UserData *udata); //更新离线识别语法词典
int run_asr(UserData *udata); //进行离线语法识别

const char* get_audio_file(void)
{
	char key = 0;
	while(key != 27) //按Esc则退出
	{
		printf("请选择音频文件：\n");
		printf("1.打电话给丁伟\n");
		printf("2.打电话给黄辣椒\n");
		printf("3.语音识别\n");				//	增加的 服务器的功能
		key = getchar();
		getchar();
		switch(key)
		{
		case '1':
			printf("\n1.打电话给丁伟\n");
			return "wav/ddhgdw.pcm";
		case '2':
			printf("\n2.打电话给黄辣椒\n");
			return "wav/ddhghlj.pcm";
		case '3':							//	增加的 服务器的功能	
			printf("\n3.语音识别\n");		//	增加的 服务器的功能
			return "wav/yysb.pcm";			//	增加的 服务器的功能
		default:
			continue;
		}
	}
	exit(0);
	return NULL;
}

int build_grm_cb(int ecode, const char *info, void *udata)
{
	UserData *grm_data = (UserData *)udata;

	if (NULL != grm_data) {
		grm_data->build_fini = 1;
		grm_data->errcode = ecode;
	}

	if (MSP_SUCCESS == ecode && NULL != info) {
		printf("构建语法成功！ 语法ID:%s\n", info);
		if (NULL != grm_data)
			snprintf(grm_data->grammar_id, MAX_GRAMMARID_LEN - 1, info);
	}
	else
		printf("构建语法失败！%d\n", ecode);

	return 0;
}

int build_grammar(UserData *udata)
{
	FILE *grm_file                           = NULL;
	char *grm_content                        = NULL;
	unsigned int grm_cnt_len                 = 0;
	char grm_build_params[MAX_PARAMS_LEN]    = {0};
	int ret                                  = 0;

	grm_file = fopen(GRM_FILE, "rb");	
	if(NULL == grm_file) {
		printf("打开\"%s\"文件失败！[%s]\n", GRM_FILE, strerror(errno));
		return -1; 
	}

	fseek(grm_file, 0, SEEK_END);
	grm_cnt_len = ftell(grm_file);
	fseek(grm_file, 0, SEEK_SET);

	grm_content = (char *)malloc(grm_cnt_len + 1);
	if (NULL == grm_content)
	{
		printf("内存分配失败!\n");
		fclose(grm_file);
		grm_file = NULL;
		return -1;
	}
	fread((void*)grm_content, 1, grm_cnt_len, grm_file);
	grm_content[grm_cnt_len] = '\0';
	fclose(grm_file);
	grm_file = NULL;

	snprintf(grm_build_params, MAX_PARAMS_LEN - 1, 
		"engine_type = local, \
		asr_res_path = %s, sample_rate = %d, \
		grm_build_path = %s, ",
		ASR_RES_PATH,
		SAMPLE_RATE_16K,
		GRM_BUILD_PATH
		);
	ret = QISRBuildGrammar("bnf", grm_content, grm_cnt_len, grm_build_params, build_grm_cb, udata);

	free(grm_content);
	grm_content = NULL;

	return ret;
}

int update_lex_cb(int ecode, const char *info, void *udata)
{
	UserData *lex_data = (UserData *)udata;

	if (NULL != lex_data) {
		lex_data->update_fini = 1;
		lex_data->errcode = ecode;
	}

	if (MSP_SUCCESS == ecode)
		printf("更新词典成功！\n");
	else
		printf("更新词典失败！%d\n", ecode);

	return 0;
}

int update_lexicon(UserData *udata)
{
	const char *lex_content                   = "丁伟\n黄辣椒";
	unsigned int lex_cnt_len                  = strlen(lex_content);
	char update_lex_params[MAX_PARAMS_LEN]    = {0}; 

	snprintf(update_lex_params, MAX_PARAMS_LEN - 1, 
		"engine_type = local, text_encoding = UTF-8, \
		asr_res_path = %s, sample_rate = %d, \
		grm_build_path = %s, grammar_list = %s, ",
		ASR_RES_PATH,
		SAMPLE_RATE_16K,
		GRM_BUILD_PATH,
		udata->grammar_id);
	return QISRUpdateLexicon(LEX_NAME, lex_content, lex_cnt_len, update_lex_params, update_lex_cb, udata);
}

int run_asr(UserData *udata)
{
	char asr_params[MAX_PARAMS_LEN]    = {0};
	const char *rec_rslt               = NULL;
	const char *session_id             = NULL;
	const char *asr_audiof             = NULL;
	FILE *f_pcm                        = NULL;
	char *pcm_data                     = NULL;
	long pcm_count                     = 0;
	long pcm_size                      = 0;
	//int last_audio                     = 0;
	int aud_stat                       = MSP_AUDIO_SAMPLE_CONTINUE;
	int ep_status                      = MSP_EP_LOOKING_FOR_SPEECH;
	int rec_status                     = MSP_REC_STATUS_INCOMPLETE;
	int rss_status                     = MSP_REC_STATUS_INCOMPLETE;
	int errcode                        = -1;

	asr_audiof = "./wav/yysb.pcm";	//	这里写死了 固定去做 语音识别 文件
	f_pcm = fopen(asr_audiof, "rb");
	if (NULL == f_pcm) {
		printf("打开\"%s\"失败！[%s]\n", asr_audiof, strerror(errno));
		goto run_error;
	}
	fseek(f_pcm, 0, SEEK_END);
	pcm_size = ftell(f_pcm);
	fseek(f_pcm, 0, SEEK_SET);
	pcm_data = (char *)malloc(pcm_size);
	if (NULL == pcm_data)
		goto run_error;
	fread((void *)pcm_data, pcm_size, 1, f_pcm);
	fclose(f_pcm);
	f_pcm = NULL;

	//离线语法识别参数设置
	snprintf(asr_params, MAX_PARAMS_LEN - 1, 
		"engine_type = local, \
		asr_res_path = %s, sample_rate = %d, \
		grm_build_path = %s, local_grammar = %s, \
		result_type = xml, result_encoding = UTF-8, ",
		ASR_RES_PATH,
		SAMPLE_RATE_16K,
		GRM_BUILD_PATH,
		udata->grammar_id
		);
	session_id = QISRSessionBegin(NULL, asr_params, &errcode);
	if (NULL == session_id)
		goto run_error;
	printf("开始识别...\n");

	while (1) {
		unsigned int len = 6400;

		if (pcm_size < 12800) {
			len = pcm_size;
			//last_audio = 1;
		}

		aud_stat = MSP_AUDIO_SAMPLE_CONTINUE;

		if (0 == pcm_count)
			aud_stat = MSP_AUDIO_SAMPLE_FIRST;

		if (len <= 0)
			break;

		printf(">");
		fflush(stdout);
		errcode = QISRAudioWrite(session_id, (const void *)&pcm_data[pcm_count], len, aud_stat, &ep_status, &rec_status);
		if (MSP_SUCCESS != errcode)
			goto run_error;

		pcm_count += (long)len;
		pcm_size -= (long)len;

		//检测到音频结束
		if (MSP_EP_AFTER_SPEECH == ep_status)
			break;

		usleep(150 * 1000); //模拟人说话时间间隙
	}
	//主动点击音频结束
	QISRAudioWrite(session_id, (const void *)NULL, 0, MSP_AUDIO_SAMPLE_LAST, &ep_status, &rec_status);

	free(pcm_data);
	pcm_data = NULL;

	//获取识别结果
	while (MSP_REC_STATUS_COMPLETE != rss_status && MSP_SUCCESS == errcode) {
		rec_rslt = QISRGetResult(session_id, &rss_status, 0, &errcode);
		usleep(150 * 1000);
	}
	printf("\n识别结束：\n");
	printf("=============================================================\n");
	if (NULL != rec_rslt)
	{	//	增加的 多条语句括起来
		printf("%s\n", rec_rslt);			//	识别成功的结果可以回发给客户端板子
		int r = sendto(sockfd,rec_rslt,strlen(rec_rslt) + 1,0,(struct sockaddr *)&client,addrlen);		//	增加的 回复识别情况
		printf("识别结果发送完成  %d\n",r);	//	增加的
	}	//	增加的 多条语句括起来
	else
	{	//	增加的 多条语句括起来
		printf("没有识别结果！\n");			//	识别不成功的结果也可以回发给客户端板子
		int r = sendto(sockfd,"没有识别结果!!!",strlen("没有识别结果!!!") + 1,0,(struct sockaddr *)&client,addrlen);	//	增加的 回复识别情况
		printf("识别结果发送完成  %d\n",r);	//	增加的
	}	//	增加的 多条语句括起来
	printf("识别结果发送完成\n");			//	增加的 识别结果提示
	printf("=============================================================\n");

	goto run_exit;

run_error:
	if (NULL != pcm_data) {
		free(pcm_data);
		pcm_data = NULL;
	}
	if (NULL != f_pcm) {
		fclose(f_pcm);
		f_pcm = NULL;
	}
run_exit:
	QISRSessionEnd(session_id, NULL);
	return errcode;
}

//	InetServerInit	增加的一个函数
/*
	InetServerInit:	服务器的初始化  
	返回值:
		成功返回0  
		失败返回其他值
*/
int InetServerInit(void)
{
	//1 创建一个套接字
	sockfd = socket(AF_INET,SOCK_DGRAM,0);		//	IPv4 数据报套接字 私有协议的 网络套接字
	if(-1 == sockfd)
	{
		perror("socket error");
		return -1;
	}
	
	//	可以重用端口
	int optval = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEPORT,&optval,sizeof(int));
                      
	
	//2 绑定自己的地址
	struct sockaddr_in ser;
	memset(&ser,0,sizeof(ser));	//	清零操作
	ser.sin_family = AF_INET;	//	网络协议为 IPv4
	ser.sin_port = htons(8899);	//	将 8899 这个端口号转成网络字节序
	ser.sin_addr.s_addr = inet_addr("192.168.31.11");
	
	int r = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
	if(-1 == r)
	{
		perror("bind error");
		return -2;
	}
	
	return 0;
}

//	InetServer	增加的一个函数
/*
	InetServer:	从客户端接收文件内容,固定写入到./wav/yysb.pcm 中
*/  
void InetServer(void)
{
	memset(&client,0,sizeof(client));		//	清零操作
	//收发   做一个回显服务器
	unsigned char buf[1024] = {0};			//	用来保存我们收到的信息
	int r;
loop:
	printf("line = %d\n", __LINE__ );
	//开始   服务器只能接收客户端的信息
	//等待客户端给的信号  我们是以 start!!! 为开始信号的
	r = recvfrom(sockfd,buf,1024,0,(struct sockaddr *)&client,&addrlen);
	printf("line = %d\n", __LINE__ );		//	显示行号
	printf("------\n");
	//看它是否发送的是起始信号
	if(!strcmp((const char *)buf,"start!!!"))
	{
		
		//	以截短的形式打开语音文件  也就是做清空文件内容
		int fd = open("./wav/yysb.pcm",O_RDWR | O_CREAT | O_TRUNC,0664);	
		if(-1 == fd)
		{
			perror("open error");
			sendto(sockfd,"error!!!",6,0,(struct sockaddr *)&client,addrlen);
			goto loop;
		}
		
		sendto(sockfd,"ok!!!",6,0,(struct sockaddr *)&client,addrlen);
			
		printf("开始接收\n");
		//	开始循环接收
		while(1)
		{
			r = recvfrom(sockfd,buf,1024,0,(struct sockaddr *)&client,&addrlen);
			if(!strcmp((const char *)buf,"quit!!!"))//收到的是退出
			{
				printf("quit\n");
				close(fd);
				break;
			}	
			//	将接收到的内容写入到文件
			write(fd,buf,r);	
			sendto(sockfd,"next!!!",8,0,(struct sockaddr *)&client,addrlen);	
		}
			
	}		
}

//	393~401 是增加的
int main(int argc, char* argv[])
{
	//	1、	初始化服务器		增加的
	printf("初始化服务器\n");
	if(InetServerInit())		//	初始化的返回值如果不等于0  会进入这个if  初始化失败
	{
		return -1;
	}
	printf("服务器初始化完成\n");
	
	//	2、	登录科大讯飞
	const char *login_config    = "appid = d2bda845"; //登录参数
	UserData asr_data; 
	int ret                     = 0 ;
	//	char c;					//	修改的,不需要了

	ret = MSPLogin(NULL, NULL, login_config); //第一个参数为用户名，第二个参数为密码，传NULL即可，第三个参数是登录参数
	if (MSP_SUCCESS != ret) {
		printf("登录失败：%d\n", ret);
		goto exit;
	}

	//	3、	构建我们的离线语法  这个规则我们已经将其固定在  yysb.bnf这个文件里面
	memset(&asr_data, 0, sizeof(UserData));
	printf("构建离线识别语法网络...\n");
	ret = build_grammar(&asr_data);  //第一次使用某语法进行识别，需要先构建语法网络，获取语法ID，之后使用此语法进行识别，无需再次构建
	if (MSP_SUCCESS != ret) {
		printf("构建语法调用失败！\n");
		goto exit;
	}
	while (1 != asr_data.build_fini)
		usleep(300 * 1000);
	if (MSP_SUCCESS != asr_data.errcode)
		goto exit;
	printf("离线识别语法网络构建完成，开始识别...\n");	
	//	427~448 修改的,不需要更新
	/*ret = run_asr(&asr_data);
	if (MSP_SUCCESS != ret) {
		printf("离线语法识别出错: %d \n", ret);
		goto exit;
	}

	printf("更新离线语法词典...\n");
	ret = update_lexicon(&asr_data);  //当语法词典槽中的词条需要更新时，调用QISRUpdateLexicon接口完成更新
	if (MSP_SUCCESS != ret) {
		printf("更新词典调用失败！\n");
		goto exit;
	}
	while (1 != asr_data.update_fini)
		usleep(300 * 1000);
	if (MSP_SUCCESS != asr_data.errcode)
		goto exit;
	printf("更新离线语法词典完成，开始识别...\n");
	ret = run_asr(&asr_data);
	if (MSP_SUCCESS != ret) {
		printf("离线语法识别出错: %d \n", ret);
		goto exit;
	}*/
	//	450~465 增加的
	while(1)
	{
		//4 做文件接收 -> 接收客户端传过来的语音文件内容
		printf("等待文件的传输\n");
		InetServer();//接收文件   接收文件完成才开始识别
		printf("语音文件传输完成，开始识别\n");
		
		//5 识别你刚刚接收到的语音文件
			//识别出来的结果就会回发给客户端
		ret = run_asr(&asr_data);
		if (MSP_SUCCESS != ret) {
			printf("离线语法识别出错: %d \n", ret);
			goto exit;
		}
	}

exit:
	MSPLogout();
	printf("请按任意键退出...\n");
	getchar();
	close(sockfd);	//	关闭套接字	增加的
	return 0;
}

